/*
   深度学习神经网络V1.0
   made by xyt
   2015/7/23
   使用语言:C

   本程序构建多层矩阵形神经网络多输入单输出
   学习策略:随机梯度下降
   激活函数:sigmoid
   使用前必须用srand((unsigned)time(NULL))取随机映射初始值
   */
#ifndef _DNN_H_
#define _DNN_H_
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <time.h>
#define DNN_VEC 8  //输入训练组组数
#define DNN_INUM 5  //输入维度
double dnn_sig(double in)   //sigmoid函数,此处不可变
{
    return 1.0/(1.0+exp(-1.0*in));
}
struct dnn_cell   //神经元结构体
{
    double w[DNN_INUM];
    double wb;
    double in[DNN_INUM];
    double out;
    double error;
    double v;
};
void SetCell_Default(struct dnn_cell *dnn_cell1);  //默认初始化，权值初始化很小
void SetCell_InitWeight(struct dnn_cell *dnn_cell1,double Initial);  //权值统一权值初始化
void SetCell_InitAll(struct dnn_cell *dnn_cell1,double Initial,double InV);  //权值统一初始化，学习速率初始化
void SetCell_Precise(struct dnn_cell *dnn_cell1,double *InW,double InWb,double InV);  //权值精确初始化，学习速率初始化
void SetIn(struct dnn_cell *dnn_cell1,double *SIn);  //设定神经元输入
double GetOut(struct dnn_cell *dnn_cell1);  //获取、设定神经元输出
void UpdateWeight(struct dnn_cell *dnn_cell1);  //更新神经元权值
void SetError(struct dnn_cell *dnn_cell1,double InErr);  //设定神经元误差传播值
void SetSpeed(struct dnn_cell *dnn_cell1,double InV);  //设定神经元学习速率
/*  //默认初始化，权值初始化很小  */
void SetCell_Default(struct dnn_cell *dnn_cell1)
{
    int i;
    for(i=0; i<DNN_INUM; i++)
    {
        dnn_cell1->w[i]=0.000001;
    }
    dnn_cell1->wb=0.000001;
    dnn_cell1->v=0.001;
}
void SetCell_InitWeight(struct dnn_cell *dnn_cell1,double Initial)   //权值统一权值初始化
{
    int i;
    for(i=0; i<DNN_INUM; i++)
    {
        dnn_cell1->w[i]=Initial;
    }
    dnn_cell1->wb=Initial;
    dnn_cell1->v=0.001;
}
void SetCell_InitAll(struct dnn_cell *dnn_cell1,double Initial,double InV)   //权值统一初始化，学习速率初始化
{
    int i;
    for(i=0; i<DNN_INUM; i++)
    {
        dnn_cell1->w[i]=Initial;
    }
    dnn_cell1->wb=Initial;
    dnn_cell1->v=InV;
}
void SetCell_Precise(struct dnn_cell *dnn_cell1,double *InW,double InWb,double InV)   //权值精确初始化，学习速率初始化
{
    int i;
    for(i=0; i<DNN_INUM; i++)
    {
        dnn_cell1->w[i]=InW[i];
    }
    dnn_cell1->wb=InWb;
    dnn_cell1->v=InV;
}
void SetIn(struct dnn_cell *dnn_cell1,double *SIn)   //设定神经元输入
{
    int i;
    for(i=0; i<DNN_INUM; i++)
    {
        dnn_cell1->in[i]=SIn[i];
    }
}
double GetOut(struct dnn_cell *dnn_cell1)   //获取、设定神经元输出
{
    int i;
    double sum=0;
    for(i=0; i<DNN_INUM; i++)
    {
        sum+=dnn_cell1->w[i]*dnn_cell1->in[i];
    }
    sum+=dnn_cell1->wb;
    dnn_cell1->out=dnn_sig(sum);
    return dnn_cell1->out;
}
void UpdateWeight(struct dnn_cell *dnn_cell1)   //更新神经元权值
{
    int i;
    for(i=0; i<DNN_INUM; i++)
    {
        dnn_cell1->w[i]-=dnn_cell1->v*dnn_cell1->error*dnn_cell1->out*(1-dnn_cell1->out)*dnn_cell1->in[i];
    }
    dnn_cell1->wb=dnn_cell1->v*dnn_cell1->error*dnn_cell1->out*(1-dnn_cell1->out);
}
void SetError(struct dnn_cell *dnn_cell1,double InErr)   //设定神经元误差传播值
{
    dnn_cell1->error=InErr;
}
void SetSpeed(struct dnn_cell *dnn_cell1,double InV)   //设定神经元学习速率
{
    dnn_cell1->v=InV;
}
/*  获得前向传播得到的输出值，第一个参数为神经元结构体数组，第二个参数为神经网络
	层数。具体排列为：前0~DNN_INUM神经元为第一层，后面每DNN_INUM个神经元为一层，依次
	排列，直至最后一个输出神经元为单独一层，如果层数是4，DNN_INUM=5（5输入）则神经元
	数量应为(4-1)*5+1=16个。*in参数为输入网络的具有DNN_INUM个数据的数组
	*/
double DNN_Cal(struct dnn_cell *incell,int deep,double *in)
{
    double out=0;
    int dd=0,i,j,k,count=0;
    double tmp[DNN_INUM];
    for(i=0; i<DNN_INUM; i++)  tmp[i]=in[i];
    for(j=0; j<deep-1; j++)
    {
        for(i=j*DNN_INUM; i<(j*DNN_INUM+DNN_INUM); i++)
        {
            SetIn(&incell[i],tmp);
            GetOut(&incell[i]);
            count++;
        }
        k=0;
        for(i=j*DNN_INUM; i<(j*DNN_INUM+DNN_INUM); i++)
        {
            tmp[k]=incell[i].out;
            k++;
        }
    }
    SetIn(&incell[count],tmp);
    out=GetOut(&incell[count]);
    return out;
}
/*
   对输入矩阵训练，最后得到更新的神经网络,要求每组数据量限定为DNN_INUM数据组数限定为DNN_VEC
   输入神经原组为按层排列，除了最后一层的节点数为一其他节点数都限定为输入向量DNN_INUM
   deep为网络层数至少2层，算上最后输出层,n为训练次数,expect为期望,返回训练后平均误差
   */
double DNN_Train(struct dnn_cell *cell,int deep,double InMat[DNN_VEC][DNN_INUM],double *expect,int n)
{
    double out,devi,sum;
    double de[DNN_VEC];
    int co=n,kp=-1;
    int i,j,k,tt,l;
    for(i=0; i<DNN_VEC; i++) de[i]=9.9;
    while(co--)
    {
        kp=(int)(rand()*(double)(DNN_VEC)/RAND_MAX);
        out=DNN_Cal(cell,deep,InMat[kp]);
        devi=out-expect[kp];
        de[kp]=devi;
        //printf("%lf  %lf  %lf  %d\n",fabs(de[0]),fabs(de[3]),fabs(de[7]),kp);
        tt=(deep-1)*DNN_INUM;
        cell[tt].error=devi;
        l=0;
        for(i=(deep-2)*DNN_INUM; i<tt; i++)
        {
            cell[i].error=cell[tt].error*cell[tt].out*(1-cell[tt].out)*cell[tt].w[l];
            l++;
        }
        for(j=deep-2; j>0; j--)
        {
            l=0;
            for(i=(j-1)*DNN_INUM; i<j*DNN_INUM; i++)
            {
                sum=0;
                for(k=j*DNN_INUM; k<(j+1)*DNN_INUM; k++)
                {
                    sum+=cell[k].error*cell[k].out*(1-cell[k].out)*cell[k].w[l];
                }
                cell[i].error=sum;
                l++;
            }
        }
        for(i=0; i<=(deep-1)*DNN_INUM; i++)
        {
            UpdateWeight(&cell[i]);
        }
        //变学习速率，可以自行更改===============================
        for(i=0; i<=(deep-1)*DNN_INUM; i++)
        {
            SetSpeed(&cell[i],fabs(devi));
        }
        //=======================================================
    }
    sum=0;
    for(i=0; i<DNN_VEC; i++) sum+=fabs(de[i]);
    return sum/DNN_VEC;
}
#endif
